package joe;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.concurrent.atomic.AtomicInteger;
import org.jetbrains.annotations.NotNull;
import org.tinylog.Logger;

public class Cleaner {
	protected static class Node extends PhantomReference<Object> {
		private @NotNull Node prev, next;

		private Node() { // only for head
			super(null, null);
			prev = this;
			next = this;
		}

		protected Node(@NotNull Object obj, @NotNull Cleaner cleaner) {
			super(obj, cleaner.refQueue);
			var head = cleaner.head;
			var hn = head.next;
			prev = head;
			next = hn;
			hn.prev = this;
			head.next = this;
			cleaner.refCount++;
		}

		private void fireClean() {
			var p = prev;
			var n = next;
			p.next = n;
			n.prev = p;
			try {
				clean();
			} catch (Exception e) { // logger.error
				Logger.error(e, "Cleaner: clean exception");
			}
		}

		protected void clean() {
		}
	}

	private static final AtomicInteger globalRefCount = new AtomicInteger();
	private static final @NotNull java.lang.ref.Cleaner globalCleaner = java.lang.ref.Cleaner.create(r -> {
		var thread = new Thread(r, "GlobalCleaner");
		thread.setPriority(Thread.NORM_PRIORITY + 2);
		return thread;
	});

	/**
	 * @param cleaner will be run in 'GlobalCleaner' thread
	 */
	public static void addGlobal(@NotNull Object obj, @NotNull Runnable cleaner) {
		globalRefCount.getAndIncrement();
		globalCleaner.register(obj, () -> {
			globalRefCount.getAndDecrement();
			cleaner.run();
		});
	}

	public static int getGlobalRefCount() {
		return globalRefCount.get();
	}

	private final ReferenceQueue<Object> refQueue = new ReferenceQueue<>();
	private final Node head = new Node();
	private int refCount;

	Cleaner() {
	}

	public int getRefCount() {
		return refCount;
	}

	public void poll() {
		int n = 0;
		for (Reference<?> r; (r = refQueue.poll()) != null; ) {
			n++;
			((Node)r).fireClean();
		}
		refCount -= n;
	}
}
